﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI;
using System.Drawing;
using System.Globalization;

namespace GoodStuff.Web.Controls
{
    /// <summary>
    /// This control allows 
    /// </summary>
    public class ImageCropping : Control, INamingContainer
    {
        private System.Web.UI.WebControls.HiddenField X;
        private System.Web.UI.WebControls.HiddenField Y;
        private System.Web.UI.WebControls.HiddenField W;
        private System.Web.UI.WebControls.HiddenField H;

        public ImageCropping()
        {
            BoxWidth = 300;
            BoxHeight = 200;
            RequiredWidth = 100;
            AspectRatio = 3;
            this.FixOrientation = false;
            this.JQueryEvent = jQueryEventHook.DocumentReady;
        }

        public double? AspectRatio
        {
            get
            {
                object o = ViewState["AspectRatio"];
                if (o is double?)
                {
                    return (double?)o;
                }
                return null;
            }
            set { ViewState["AspectRatio"] = value; }
        }

        public int BoxWidth
        {
            get
            {
                object o = ViewState["BoxWidth"];
                if (o is int)
                {
                    return (int)o;
                }
                return 0;
            }
            set { ViewState["BoxWidth"] = value; }
        }

        public int RequiredWidth
        {
            get
            {
                object o = ViewState["RequiredWidth"];
                if (o is int)
                {
                    return (int)o;
                }
                return 0;
            }
            set { ViewState["RequiredWidth"] = value; }
        }

        public int BoxHeight
        {
            get
            {
                object o = ViewState["BoxHeight"];
                if (o is int)
                {
                    return (int)o;
                }
                return 0;
            }
            set { ViewState["BoxHeight"] = value; }
        }

        public string TargetImage
        {
            get
            {
                return ViewState.GetValue<string>("TargetImage");
            }
            set { ViewState["TargetImage"] = value; }
        }

        /// <summary>
        /// iPhone images are always stored the same way regardless of how the phone is held, but a flag is set in the EXIF data that specifies which orientation the image should be in.
        /// Specify true to check and fix the orientation (default is false).
        /// </summary>
        public bool FixOrientation { get; set; }

        public jQueryEventHook JQueryEvent { get; set; }

        /// <summary>
        /// After a postback, JQuery will have injected the coordinates on the given image.
        /// Example. btnCrop.Click += { PerformCrop(); }
        /// </summary>
        public System.Drawing.Image PerformCrop(System.Drawing.Image img, bool sizeToTarget)
        {
            EnsureChildControls();

            if (string.IsNullOrEmpty(X.Value))
            {
                //cropping has not been performed (yet).
                return null;
            }

            if (this.FixOrientation)
            {
                this.CheckAndFixOrientation(img);
            }

            int x = (int)float.Parse(this.X.Value, CultureInfo.InvariantCulture.NumberFormat);
            int y = (int)float.Parse(this.Y.Value, CultureInfo.InvariantCulture.NumberFormat);
            int w = (int)float.Parse(this.W.Value, CultureInfo.InvariantCulture.NumberFormat);
            int h = (int)float.Parse(this.H.Value, CultureInfo.InvariantCulture.NumberFormat);

            int newW = w;
            int newH = h;

            if (sizeToTarget)
            {
                float ratio = (float)h / (float)w;

                newW = RequiredWidth;
                newH = (int)(RequiredWidth * ratio);
            }

            System.Drawing.Bitmap _bitmap = new System.Drawing.Bitmap(newW, newH);

            _bitmap.SetResolution(img.HorizontalResolution, img.VerticalResolution);
            using (Graphics _graphic = Graphics.FromImage(_bitmap))
            {
                _graphic.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                _graphic.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                _graphic.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
                _graphic.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
                //_graphic.DrawImage(img, 0, 0, w, h);
                _graphic.DrawImage(img, new Rectangle(0, 0, newW, newH), x, y, w, h, GraphicsUnit.Pixel);
            }

            return _bitmap;
        }

        protected override void OnPreRender(EventArgs e)
        {
            if (string.IsNullOrEmpty(TargetImage))
            {
                throw new ArgumentNullException("TargetImage");
            }
            Control targetImage = this.Parent.FindControl(TargetImage);
            if (targetImage == null)
            {
                throw new ArgumentException(string.Format("'{0}' must be a control next ImageCropping '{1}'", targetImage, this.ID));
            }

            string jQueryEventHook = string.Empty;
            switch (this.JQueryEvent)
            {
                case ImageCropping.jQueryEventHook.WindowLoad:
                    jQueryEventHook = "$(window).load(function() {";
                    break;
                default:
                    jQueryEventHook = "$(document).ready(function() {";
                    break;
            }

            Page.ClientScript.RegisterClientScriptBlock(typeof(ImageCropping), "ImageCropping", 
  jQueryEventHook + @"
    jQuery('#" + targetImage.ClientID + @"').Jcrop({ 
      onSelect: storeCoords" + this.ClientID + @",
      boxWidth: " + BoxWidth.ToString() + @", 
      boxHeight: " + BoxHeight.ToString() + @",
      aspectRatio: " + AspectRatio.Value.ToString(CultureInfo.InvariantCulture) + @",     
      setSelect: [" + X.Value + "," + Y.Value + "," + W.Value + "," + H.Value + "]" + @"
    }); 
  }); 
 
  function storeCoords" + this.ClientID + @"(c) { 
    jQuery('#" + X.ClientID + @"').val(c.x); 
    jQuery('#" + Y.ClientID + @"').val(c.y); 
    jQuery('#" + W.ClientID + @"').val(c.w); 
    jQuery('#" + H.ClientID + @"').val(c.h); 
  }; 
", true);
            base.OnPreRender(e);
        }

        protected override void CreateChildControls()
        {
            this.Controls.Clear();

            X = new System.Web.UI.WebControls.HiddenField();
            X.ID = "X";
            X.Value = "0";
            Controls.Add(X);
            Y = new System.Web.UI.WebControls.HiddenField();
            Y.ID = "Y";
            Y.Value = "0";
            Controls.Add(Y);
            W = new System.Web.UI.WebControls.HiddenField();
            W.ID = "W";
            W.Value = RequiredWidth.ToString();
            Controls.Add(W);
            H = new System.Web.UI.WebControls.HiddenField();
            H.ID = "H";

            int initialHeight = (int)(RequiredWidth / AspectRatio);
            H.Value = initialHeight.ToString(CultureInfo.InvariantCulture);
            Controls.Add(H);
            this.ChildControlsCreated = true;
        }

        /// <summary>
        /// iPhone images are always stored the same way regardless of how the phone is held, but a flag is set in the EXIF data that specifies which orientation the image should be in. 
        /// Almost all native OSX applications such as iPhoto and Preview can read this EXIF orientation tag correctly and rotates the image automatically, but almost all Windows applications 
        /// and web browsers don't take the orientation EXIF tag into account. You'll have to manually rotate the image on the web server before saving it
        /// </summary>
        /// <param name="image"></param>
        /// <remarks>http://stackoverflow.com/questions/4565507/camera-image-changes-orientation</remarks>
        private void CheckAndFixOrientation(Image image)
        {
            // 0x0112 is the EXIF byte address for the orientation tag
            if (image.PropertyIdList.Contains(0x0112) == false)
            {
                return;
            }

            // get the first byte from the orientation tag and convert it to an integer
            var orientationNumber = image.GetPropertyItem(0x0112).Value[0];

            switch (orientationNumber)
            {
                // up is pointing to the right
                case 8:
                    image.RotateFlip(RotateFlipType.Rotate270FlipNone);
                    break;
                // up is pointing to the bottom (image is upside-down)
                case 3:
                    image.RotateFlip(RotateFlipType.Rotate180FlipNone);
                    break;
                // up is pointing to the left
                case 6:
                    image.RotateFlip(RotateFlipType.Rotate90FlipNone);
                    break;
                // up is pointing up (correct orientation)
                case 1:
                    break;
            }
        }

        public enum jQueryEventHook
        {
            /// <summary>
            /// Executes when the DOM is fully loaded.
            /// </summary>
            DocumentReady,

            /// <summary>
            /// Bind an event handler to the “load” JavaScript event.
            /// </summary>
            WindowLoad
        }
    }
}
